<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Visual Mapping</title>
    <style>
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
    </style>
  </head>

  <body>
    <div id="tip">
      节点：颜色 —— cluster；大小 —— value；形状 —— subCluster <br />边：颜色 —— 起始点颜色；粗细 ——
      weight
    </div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script src="./assets/jquery-3.2.1.min.js"></script>
    <script>
      const colors = [
        'rgb(64, 174, 247)',
        'rgb(108, 207, 169)',
        'rgb(157, 223, 125)',
        'rgb(240, 198, 74)',
      ];
      const shapes = ['circle', 'rect'];

      const data = {
        nodes: [
          {
            id: '0',
            label: '0',
            value: 10,
            cluster: 'a',
            subCluster: 'sa',
          },
          {
            id: '1',
            label: '1',
            value: 20,
            cluster: 'b',
            subCluster: 'sb',
          },
          {
            id: '2',
            label: '2',
            value: 5,
            cluster: 'a',
            subCluster: 'sa',
          },
          {
            id: '3',
            label: '3',
            value: 10,
            cluster: 'a',
            subCluster: 'sa',
          },
          {
            id: '4',
            label: '4',
            value: 12,
            cluster: 'c',
            subCluster: 'sb',
          },
          {
            id: '5',
            label: '5',
            value: 18,
            cluster: 'c',
            subCluster: 'sa',
          },
          {
            id: '6',
            label: '6',
            value: 3,
            cluster: 'c',
            subCluster: 'sa',
          },
          {
            id: '7',
            label: '7',
            value: 7,
            cluster: 'b',
            subCluster: 'sa',
          },
          {
            id: '8',
            label: '8',
            value: 21,
            cluster: 'd',
            subCluster: 'sb',
          },
          {
            id: '9',
            label: '9',
            value: 9,
            cluster: 'd',
            subCluster: 'sb',
          },
        ],
        edges: [
          {
            source: '0',
            target: '1',
            weight: 20,
          },
          {
            source: '0',
            target: '3',
            weight: 12,
          },
          {
            source: '0',
            target: '4',
            weight: 19,
          },
          {
            source: '0',
            target: '5',
            weight: 2,
          },
          {
            source: '0',
            target: '7',
            weight: 8,
          },
          {
            source: '0',
            target: '8',
            weight: 14,
          },
          {
            source: '0',
            target: '9',
            weight: 5,
          },
          {
            source: '2',
            target: '3',
            weight: 20,
          },
          {
            source: '4',
            target: '5',
            weight: 10,
          },
          {
            source: '4',
            target: '6',
            weight: 11,
          },
        ],
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 800,
        layout: {
          type: 'force',
          linkDistance: 100,
          nodeStrength: -30,
          edgeStrength: 0.1,
        },
        modes: {
          default: [
            {
              type: 'tooltip',
              formatText(model) {
                const text =
                  'NODE' +
                  '<br/> id: ' +
                  model.id +
                  '<br/> cluster: ' +
                  model.cluster +
                  '<br/> subCluster: ' +
                  model.subCluster +
                  '<br/> value: ' +
                  model.value;
                return text;
              },
              shouldUpdate: e => {
                return true;
              },
            },
            {
              type: 'edge-tooltip',
              formatText(model) {
                const text =
                  'EDGE' +
                  '<br/> source: ' +
                  model.source +
                  '<br/> weight: ' +
                  model.weight +
                  '<br/> target: ' +
                  model.target;
                return text;
              },
              shouldUpdate: e => {
                return true;
              },
            },
          ],
        },
        defaultNode: {
          size: [10, 10],
          color: 'steelblue',
          style: {
            opacity: 0.8,
            lineWidth: 1,
            stroke: '#999',
          },
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2',
          style: {
            opacity: 0.6,
            lineAppendWidth: 5,
          },
        },
      });

      // mapping
      const edges = data.edges;
      const nodes = data.nodes;
      const nodeMap = new Map();
      const clusterMap = new Map();
      const subClusterMap = new Map();
      let clusterId = 0;
      let subClusterId = 0;
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        // cluster
        if (node.cluster && clusterMap.get(node.cluster) === undefined) {
          clusterMap.set(node.cluster, clusterId);
          clusterId++;
        }
        // subCluster
        if (node.subCluster && subClusterMap.get(node.subCluster) === undefined) {
          subClusterMap.set(node.subCluster, subClusterId);
          subClusterId++;
        }
        const cid = clusterMap.get(node.cluster);
        const scid = subClusterMap.get(node.subCluster);
        if (node.style) {
          node.style.fill = colors[cid % colors.length];
        } else {
          node.style = {
            fill: colors[cid % colors.length],
          };
        }
        node.shape = shapes[scid % shapes.length];
      });

      // map the value to node size
      let maxEdgeWeight = -9999,
        minEdgeWeight = 9999;
      edges.forEach((edge, i) => {
        const source = nodeMap.get(edge.source);
        const target = nodeMap.get(edge.target);
        edge.color = source.style.fill;
        // lineWidth
        if (maxEdgeWeight < edge.weight) maxEdgeWeight = edge.weight;
        if (minEdgeWeight > edge.weight) minEdgeWeight = edge.weight;
      });

      // map the value to node size
      let maxNodeValue = -9999,
        minNodeValue = 9999;
      nodes.forEach(n => {
        if (maxNodeValue < n.value) maxNodeValue = n.value;
        if (minNodeValue > n.value) minNodeValue = n.value;
      });
      const nodeSizeRange = [10, 30];
      const nodeSizeDataRange = [minNodeValue, maxNodeValue];
      const lineWidthRange = [0.5, 3];
      const lineWidthDataRange = [minEdgeWeight, maxEdgeWeight];
      scaleNodeProp(nodes, 'size', 'value', nodeSizeDataRange, nodeSizeRange);
      scaleNodeProp(edges, 'style.lineWidth', 'weight', lineWidthDataRange, lineWidthRange);

      graph.data(data);
      graph.render();
      function scaleNodeProp(elements, propName, refPropName, dataRange, outRange) {
        const outLength = outRange[1] - outRange[0];
        const dataLength = dataRange[1] - dataRange[0];
        elements.forEach(n => {
          if (propName.split('.')[0] === 'style') {
            if (n.style) {
              n.style[propName.split('.')[1]] =
                ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0];
            } else {
              n.style = {
                [propName.split('.')[1]]:
                  ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0],
              };
            }
          } else {
            n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0];
          }
        });
      }
    </script>
  </body>
</html>
